Theming
Theming & Customization Guide
Use this guide to change any part of Vyasa's look and behavior. It covers where to place CSS, how scoping works, which elements to target, and how to change behavior safely.
1) Where to put your CSS
Vyasa loads custom CSS in this order (later wins):
- Framework CSS (bundled in the app)
- Root CSS:
custom.cssorstyle.cssat your blog root - Folder CSS:
custom.cssorstyle.cssinside a folder (scoped to that folder)
Your blog root is determined by VYASA_ROOT or a .vyasa config file. If neither is set, Vyasa uses the current working directory.
Root-level CSS (site-wide)
Create or edit:
/your-blog-root/custom.css(preferred)/your-blog-root/style.css
This applies everywhere.
Folder-level CSS (scoped)
Place a custom.css or style.css in any content folder to style just that folder (and its subfolders).
Vyasa wraps that CSS inside a scoped selector:
#main-content.section-your-folder { ... }
This uses CSS nesting. Modern browsers support this. You can write normal selectors inside your folder CSS and Vyasa will nest them under the correct section automatically.
2) How to find the section scope class
Folder CSS is scoped to the #main-content element with a section class derived from the path.
Example:
demo/books/flat-land/chapter-01.md- Section class on
#main-contentbecomes:section-demo-books-flat-land
To confirm, inspect #main-content in your browser DevTools and copy the class.
3) DOM map (what you can target)
Use these ids/classes to style specific elements.
How to find selectors for obscure elements
If an element isn't listed here, you can find its selector quickly:
- Use DevTools first: right‑click the element → Inspect → note the
idor classes on the highlighted node. - Search in source: open
vyasa/core.py(live app) andvyasa/build.py(static build), then search for the element’s text or a nearby class name. - Search for ids/classes: in the repo, run a text search for the class or id you saw in DevTools.
- Follow the builder: most markup is created in
layout()ornavbar(); those functions assemble the page chrome and are the easiest places to trace where a class or id is set. - Look for generated HTML: if the element is created from Markdown, check render functions like
render_footnote_ref()and the tab/mermaid helpers incore.py.
What to do after you find a selector
Once you have the selector (id/class/tag), add a rule in custom.css (or a folder custom.css if you want section‑only styling), then reload and refine.
Example:
<aside id="posts-sidebar" class="hidden xl:block w-72 ...">
/* Root custom.css */
#posts-sidebar {
background: #f3f4f6;
border-radius: 12px;
padding: 0.5rem;
}
If your rule doesn’t apply:
- Increase specificity: target a deeper element (e.g.,
#posts-sidebar ul) - Add
!importantto the property being overridden - Check scope: if you used folder
custom.css, confirm the page path matches the section
Page structure
#page-container- outer wrapper for the whole page#site-navbar- sticky header wrapper#content-with-sidebars- row containing sidebars + main content#main-content- the rendered post content#site-footer- footer wrapper
Sidebars
#posts-sidebar- left navigation tree#toc-sidebar- right table of contents#sidebar-scroll-container- scrollable list container.toc-link- each TOC link
Mobile panels
#mobile-posts-panel,#mobile-toc-panel- off-canvas panels#mobile-posts-toggle,#mobile-toc-toggle- toggle buttons
Markdown content
h1…h6,p,ul,ol,blockquote,table,code,pre,img.mermaid-wrapperand.mermaidfor Mermaid diagrams.sidenote-ref,.sidenote,.sidenote.hlfor footnotes.tabs-container,.tabs-header,.tab-button,.tabs-content,.tab-panelfor tabs
4) Global theming (background, typography, links)
Put this in root custom.css to define a new global look:
/* Global background + text */
html, body {
background-color: #f6f3ee !important;
color: #1f2937 !important;
}
#page-container,
#main-content {
background-color: transparent;
color: inherit;
}
.dark html, .dark body {
background-color: #0b0f14 !important;
color: #e2e8f0 !important;
}
/* Typography */
body {
font-family: "IBM Plex Sans", system-ui, sans-serif;
line-height: 1.7;
}
h1, h2, h3 {
letter-spacing: -0.02em;
}
/* Links */
a { color: #0f766e; }
a:hover { color: #115e59; }
5) Navbar and footer
The navbar is the first child inside #site-navbar and the footer content is inside #site-footer > div.
#site-navbar > div {
background-color: #0f766e !important;
color: #f8fafc !important;
}
#site-footer > div {
background-color: #1f2937 !important;
color: #f8fafc !important;
}
#site-navbar a,
#site-footer a {
color: #f8fafc;
}
#site-navbar a:hover,
#site-footer a:hover {
color: #e2e8f0;
}
.dark #site-navbar > div { background-color: #0b3b3a !important; }
.dark #site-footer > div { background-color: #111827 !important; }
6) Sidebars and TOC
/* Left posts sidebar */
#posts-sidebar {
background: #f3f4f6;
border-radius: 12px;
padding: 0.5rem;
}
/* TOC sidebar */
#toc-sidebar {
background: #f8fafc;
border-radius: 12px;
padding: 0.5rem;
}
/* TOC links */
.toc-link {
color: #0f172a !important;
border-radius: 8px;
}
.toc-link:hover {
background: rgba(15, 118, 110, 0.12);
color: #0f766e !important;
}
7) Code blocks and inline code
pre {
background: #0b1020;
color: #e2e8f0;
border-radius: 12px;
padding: 1rem 1.25rem;
}
code {
background: rgba(15, 118, 110, 0.12);
color: #0f766e;
padding: 0.1rem 0.35rem;
border-radius: 6px;
}
pre code {
background: transparent;
color: inherit;
padding: 0;
}
8) Mermaid diagrams
.mermaid-wrapper {
background: #f8fafc;
border-radius: 12px;
border: 1px solid #e2e8f0;
}
.dark .mermaid-wrapper {
background: #0f172a;
border-color: #1f2937;
}
9) Footnotes / sidenotes
.sidenote-ref {
font-size: 0.8rem;
padding: 0 0.2rem;
border-radius: 0.25rem;
}
.sidenote {
font-size: 0.95rem;
color: #334155;
}
.sidenote.hl {
background-color: rgba(15, 118, 110, 0.12);
}
10) Tabs
Tabs are styled by built-in CSS, but you can override:
.tabs-container {
border-radius: 14px;
border-color: #cbd5f5;
}
.tab-button.active {
border-bottom-color: #0f766e;
color: #0f766e;
}
.tabs-content {
background: #ffffff;
}
11) Images and media
img {
border-radius: 12px;
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
}
figure > img {
width: 100%;
height: auto;
}
12) Change behavior (JS + HTML)
For behavior changes (animations, interactions, logic), you have two options:
Option A: Custom JS
Edit:
vyasa/static/scripts.jsfor live appvyasa/build.py(static build) if you need it in static exports
Option B: Inline HTML/JS in Markdown
If you need per-post behavior, you can embed raw HTML in markdown:
<div id="my-widget"></div>
<script>
// Your custom behavior here
</script>
13) Advanced: change the HTML structure
If you want to move elements or change layout, edit these functions:
vyasa/core.py::navbar()for the header markupvyasa/core.py::layout()for page structure and sidebarsvyasa/build.py::static_layout()for static builds
14) Troubleshooting
If your styles don't apply:
- Check your root: Is
custom.cssin the actual blog root? - Check if CSS is loaded: View page source and confirm
/posts/custom.cssis present. - HTMX swaps: Folder-scoped CSS is injected into
#scoped-css-containerand swapped during navigation. If your styles disappear, ensurecustom.cssexists in that folder. - Utility class conflicts: Use more specific selectors or
!importantto override Tailwind-style classes.
You now have full control of Vyasa's look and behavior. Start global, then layer scoped CSS for sections, then override specific elements by id/class as needed.